// Copyright (c) 2025 Huawei Technologies Co., Ltd.
// openUBMC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//         http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
//
// Description: 实现kvm客户端认证
#include "Utils.hpp"
#include "ClientBaseHttp.hpp"

ClientBaseHttp::ClientBaseHttp() : uri_(""), body_(nullptr),
    resObj_(NULL), failRes_({"",""}), httpResult_(boost::beast::http::status::unknown)
{
    ResetHttpCall();
}

ClientBaseHttp::ClientBaseHttp(boost::string_view uri) : uri_(uri), body_(nullptr),
    resObj_(NULL), failRes_({"",""}), httpResult_(boost::beast::http::status::unknown)
{
    ResetHttpCall();
}

ClientBaseHttp::~ClientBaseHttp()
{
    DeleteCharArray(body_);
    ResetHttpCall();
}

void ClientBaseHttp::SetBody(boost::string_view body)
{
    try {
        SafeStringUpdate(body_, body.data());
    } catch (...) {
        throw;
    }
}

bool ClientBaseHttp::Call(boost::string_view host, boost::string_view port, boost::string_view userName,
    const char* passWord, boost::beast::http::verb httpMethod)
{
    // 一个基于boost的使用TLS通信的HTTP请求过程
    // 该过程作为模板方法
    try {
        // 每次请求先重置派生类中用于存放响应体信息的成员变量
        ResetHttpCall();
        // 创建HTTP请求
        boost::beast::http::request<boost::beast::http::string_body> req = GetRequest(host, userName, passWord,
            httpMethod);
        boost::asio::io_context ioc;
        boost::asio::ssl::context ctx { boost::asio::ssl::context::tlsv13_client };
        ctx.set_default_verify_paths();
        boost::asio::ssl::stream<boost::asio::ip::tcp::socket> stream(ioc, ctx);
        // 创建TCP域名解析器
        boost::asio::ip::tcp::resolver resolver(ioc);
        // 解析域名
        auto const results = resolver.resolve(host.to_string(), port.to_string());
        // asio的异步连接
        boost::asio::connect(stream.next_layer(), results.begin(), results.end());
        // ssl握手
        stream.handshake(boost::asio::ssl::stream_base::client);
        // 发送请求
        boost::beast::http::write(stream, req);
        // 接收响应
        boost::beast::flat_buffer buffer;
        boost::beast::http::response<boost::beast::http::dynamic_body> res;
        boost::beast::http::read(stream, buffer, res);
        // 关闭SSL层，断开连接
        CloseStream(stream);
        // 解析响应体
        ParseResponse(res);
        DeleteCharArray(body_);
        return true;
    } catch (...) {
        throw;
    }
}

char* ClientBaseHttp::GetEncodeAuth(boost::string_view userName, const char* passWord)
{
    try {
        char* authStr = DeepCopyCstr(userName.data());
        SafeStringUpdate(authStr, SafeStrCat(authStr, ":"));
        SafeStringUpdate(authStr, SafeStrCat(authStr, passWord));
        char dest[512];
        boost::beast::detail::base64::encode(&dest[0], authStr, strlen(authStr));
        SafeStringUpdate(authStr, SafeStrCat("Basic ", dest));
        return authStr;
    } catch (...) {
        throw;
    }
}

boost::beast::http::request<boost::beast::http::string_body> ClientBaseHttp::GetRequest(boost::string_view host,
    boost::string_view userName, const char* passWord, boost::beast::http::verb httpMethod)
{
    try {
        boost::beast::http::request<boost::beast::http::string_body> req
        {
            httpMethod, uri_, 11
        };
        req.set(boost::beast::http::field::host, host);
        req.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
        req.set(boost::beast::http::field::content_type, "application/json");
        char* auth = GetEncodeAuth(userName, passWord);
        req.set(boost::beast::http::field::authorization, auth);
        DeleteCharArray(auth);
        req.body() = body_;
        req.prepare_payload();
        return req;
    } catch (...) {
        throw;
    }
}

void ClientBaseHttp::CloseStream(boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& stream)
{
    try {
        boost::system::error_code shutdown_ec, close_ec;
        auto shutdown_err = stream.shutdown(shutdown_ec);
        auto close_err = stream.next_layer().close(close_ec);
        if (shutdown_err != boost::asio::error::eof && shutdown_err != boost::asio::ssl::error::stream_truncated) {
            throw shutdown_err;
        }
        if (close_err) {
            throw close_err;
        }
    } catch (...) {
        throw;
    }
}

boost::json::value ClientBaseHttp::GetErrorArray(const boost::json::object& obj, boost::string_view cmpKey)
{
    try {
        auto errRes = boost::json::string{""};
        if (obj == NULL) {
            return errRes;
        }
        for (const auto& kv : obj) {
            auto key = kv.key();
            boost::json::value val = kv.value();
            if (key == cmpKey) {
                return val;
            }
        }
        return errRes;
    } catch (...) {
        throw;
    }
}

void ClientBaseHttp::ParseErrorCode(const boost::json::object& errorObj)
{
    try {
        for (const auto& errorInfo : errorObj) {
            auto infoKey = errorInfo.key();
            auto infoVal = errorInfo.value();
            if (infoKey == "code") {
                failRes_.errorCode = infoVal.as_string().c_str();
            }
            if (infoKey == "message") {
                failRes_.message = infoVal.as_string().c_str();
            }
        }
    } catch (...) {
        throw;
    }
}

void ClientBaseHttp::ParseErrorCodeArray(const boost::json::array& arr)
{
    try {
        for (const auto& error_kv : arr) {
            if (!error_kv.is_object()) {
                continue;
            }
            auto error_obj = error_kv.as_object();
            ParseErrorCode(error_obj);
        }
    } catch (...) {
        throw;
    }
}

void ClientBaseHttp::ParseErrorInfo()
{
    try {
        auto val = GetErrorArray(resObj_, "error");
        if (!val.is_array()) {
            return;
        }

        const auto arr = val.as_array();
        ParseErrorCodeArray(arr);
    } catch (...) {
        throw;
    }
}

void ClientBaseHttp::ParseResponse(const boost::beast::http::response<boost::beast::http::dynamic_body>& res)
{
    try {
        httpResult_ = res.result();
        std::string resBodyStr = buffers_to_string(res.body().data());

        if (resBodyStr == "") {
            boost::string_view errMsg = "Response body is null";
            throw std::logic_error(errMsg.data());
        }

        boost::json::value resJson = boost::json::parse(resBodyStr);
        resObj_ = resJson.as_object();

        if (httpResult_ == boost::beast::http::status::ok || httpResult_ == boost::beast::http::status::created) {
            ParseSuccInfo();
        } else {
            ParseErrorInfo();
        }
        resBodyStr.erase();
        resBodyStr.clear();
    } catch (...) {
        throw;
    }
}

void ClientBaseHttp::ResetFailRes()
{
    failRes_.errorCode = "";
    failRes_.message = "";
}

void ClientBaseHttp::ResetHttpCall()
{
    resObj_ = NULL;
    httpResult_ = boost::beast::http::status::unknown;
    ResetFailRes();
    ResetSuccRes();
}

void ClientBaseHttp::SetUri(boost::string_view uri)
{
    uri_ = uri.data();
}

boost::beast::http::status ClientBaseHttp::GetCallStatus()
{
    return httpResult_;
}

HttpFailInfo ClientBaseHttp::GetFailInfo()
{
    return failRes_;
}

void ClientBaseHttp::ParseSuccInfo() {}
void ClientBaseHttp::ResetSuccRes() {}
